#!/usr/bin/env python
# encoding: utf-8
"""
ics_creator.py

Created by Paul Gower on 2010-05-06.
Copyright (c) 2010 __MyCompanyName__. All rights reserved.
"""

import sys
import os
import datetime
import random
import re
import mimetypes
import optparse
import smtplib

import email
import email.message
import email.mime.text
import email.mime.multipart

import icalendar
import icalendar.tools
import icalendar.prop

from string import ascii_letters, digits

from csvprocessor import CSVProcessor

class ICSCreator(object):
  """Create ICS files and email the results"""
  def __init__(self, input_file, start_date, end_date, output_path="~/", email_body=None, email_subject=None):
    """
    Args:
      input_file: the time chart csv file to import
      start_date: the start date of the term that the time chart file represents
      end_date: the end date of the term that the time chart file represents (when to stop repeating)
      output_path: location to save the created ics files
      email_body: text to insert into the email body, <firstname> and <lastname> will be replaced with the first and last names extracted from the timechart
      email_subject: the subject to use for the email, <firstname> and <lastname> will be replaced with the first and last names extracted from the timechart
    """
    self.output_path = output_path
    self.input_file = input_file 
    self.start_date = start_date 
    self.end_date = end_date 
    self.csv = CSVProcessor(input_file)
    self.email_body = email_body or "The standard email body <firstname> <lastname>"
    self.email_subject = email_subject or "The standard email subject <firstname> <lastname>"
    self.first_name_replace = re.compile("<firstname>")
    self.last_name_replace = re.compile("<lastname>")
    
  def create_send_all_timetables(self, send=True):
    """
    Create and send a timetable for all users
    """
    persons = self.csv.get_persons()
    for person in persons:
      user_ical_file = self.create_timetable(self.csv.get_data_for_person(person),self.start_date,self.end_date)
      # write ical file to disk in temp dir
      output_file = os.path.join(self.output_path,person + "_classes.ics") # generate output file path
      open(output_file, 'wb').write(user_ical_file.as_string()) # write file contents
      if send:
        self.email_ical_file(person, output_file)
      else:
        print "Not sending: " + output_file
      
  def create_send_all_fragment_timetables(self,fragment_start_date,fragment_end_date,send=True):
    """
    Create and email fragments of the person timetables between the given fragment dates
    Args:
      fragment_start_date: the start of the fragment we want
      fragment_end_date: the end of the fragment we want
    """
    persons = self.csv.get_persons()
    for person in persons:
      user_ical_file = self.create_timetable_fragment(self.csv.get_data_for_person(person),self.start_date,self.end_date,fragment_start_date,fragment_end_date)
      # write ical file to disk in temp dir
      output_file = os.path.join(self.output_path,person + "_classes_from_" + fragment_start_date.strftime("%d_%m_%y") + "_to_" + fragment_end_date.strftime("%d_%m_%y") + ".ics") # generate output file path
      open(output_file, 'wb').write(user_ical_file.as_string()) # write file contents
      if send:
        self.email_ical_file(person, output_file)
      else:
        print "Not sending: " + output_file
      
  def email_ical_file(self, code, filename):
    """
    Email the given filename using the body and subject already provided, replace <firstname> and <lastname> with the correct values
    Args:
      filename: the filename to send with the email
      code: the code of the person we are sending email to, we can (probably) use this to generate a correct email address
    """
    name = self.csv.data['codes'][code]
    name = name.split(" ",1)
    firstname = name[0]
    lastname = name[1]
    address = code + "@stmonicas.qld.edu.au"
    subject = self.email_subject
    # replace <firstname> and <lastname> in the subject
    subject = self.first_name_replace.sub(firstname,subject)
    subject = self.last_name_replace.sub(lastname,subject)
    body = self.email_body
    if os.path.isfile(body):
      # the body passed is a file not a string, use the contents of the file as the body
      body = open(body, 'r').read()
    body = self.first_name_replace.sub(firstname,body)
    body = self.last_name_replace.sub(lastname,body)
    # create the mime message and append the file
    msg = email.mime.multipart.MIMEMultipart()
    attachment = email.mime.text.MIMEText(open(filename).read(), _subtype='calendar')
    attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(filename))
    msg.attach(attachment)
    s = smtplib.SMTP("hostname","hostport")
    s.sendmail("system@stmonicas.qld.edu.au", "gowepa0@stmonicas.qld.edu.au", msg.as_string()) #FIXME: change "gowepa0@stmonicas.qld.edu.au" to address
    print "Send calendar to: " + address
    
  def create_event(self, who, what, when, where, duration, recur):
    """
    Create an Ical event based on the information given
    Args:
      who: the name of the person who will own this event
      what: the name of the class that will be taught
      when: the start time of the lesson
      where: the room name
      duration: the duration in minutes of the lesson
    """
    e = icalendar.Event()
    e['uid'] = self.get_uid()
    e.set('created',datetime.datetime.now().strftime("%Y%m%dT%H%M%S"),encode=0)
    e.add('description',"Teacher/Class: " + who)
    e.set('dtstart',when.strftime("%Y%m%dT%H%M%S"),encode=0)
    e.set('location',"Room " + where)
    e.set('summary',what) # this appears in the title of the event
    e.set('duration',duration)
    e.set('rrule',recur)
    return e
    
  def create_day(self, day_data, day_number, day_date, recur, ical):
    """
    Create all of the events described by this day
    Args:
      day_data: a list of events for the person for one day
      day_number: the number of the day so we can apply appropriate timings (this is behind by 1, so the max day is 9, needs to be taken into account when calculating sport days etc.)
      day_date: the actual date of this day (calcualted in the timetable method)
      ical: the icalendar object that the days events are being added to
      recur: wether this day should recur, if no reccurence then this is false else this is a vRecur object with the correct recurrence
    """
    t = datetime.time
    td = datetime.timedelta
    # tz = icalendar.prop.LocalTimezone()
    normal_times = [(t(8,50),td(0,0,0,0,90)),
                    (t(10,20),td(0,0,0,0,50)),
                    (t(11,30),td(0,0,0,0,50)),
                    (t(12,20),td(0,0,0,0,50)),
                    (t(13,50),td(0,0,0,0,45)),
                    (t(14,35),td(0,0,0,0,45))]
    sport_times = [(t(8,35),td(0,0,0,0,45)),
                   (t(9,20),td(0,0,0,0,45)),
                   (t(10,05),td(0,0,0,0,45)),
                   (t(11,20),td(0,0,0,0,45)),
                   (t(12,5),td(0,0,0,0,50)),
                   (t(13,35),td(0,0,0,0,85))]
    sport_days = [3,8]
    for i, period in enumerate(day_data):
      if period:
        if day_number + 1 in sport_days: # FIXME: ive got a sneaking suspicion that there is an off by one error in here somewhere
          when = datetime.datetime.combine(day_date, sport_times[i][0])
          duration = sport_times[i][1]
        else:
          when = datetime.datetime.combine(day_date, normal_times[i][0])
          duration = normal_times[i][1]
        # FIXME: Add recurrence
        ical.add_component(self.create_event(period['who'],period['what'],when,period['where'],duration,recur))
        
      
    
  def create_timetable(self, timetable_data, start_date, end_date):
    """
    Iterates over the given timetable data and creates an Ical timetable that repeats bi-weekly within the given start and end dates
    Args:
      data: the time table data given in the format returned by csv_processor
      start_date: timetable start date
      end_date: timetable end date
    """
    ical = self.get_icalendar()
    offset = datetime.timedelta(0)
    for i,day_data in enumerate(timetable_data):
      r = icalendar.prop.vRecur(FREQ='weekly', INTERVAL=2, UNTIL=end_date)
      self.create_day(day_data, i, start_date + offset, r, ical)
      offset += datetime.timedelta(1)
      while (start_date + offset).weekday() in [5,6]:
        offset = offset + datetime.timedelta(1)
    return ical
    
  def create_timetable_fragment(self, timetable_data, tt_start_date, tt_end_date, fragment_start_date, fragment_end_date):
    """
    Create a fragment of timetable with the given start and end dates
    Args:
      timetable_data: this is the data gathered from the timechart csv, used to create the events
      tt_start_date: the timetable start date (term start date)
      tt_end_date: the timetable end date (term end date)
      fragment_start_date: the start date for the fragment (must tt_start_date < x < tt_end_date)
      fragment_end_date: the end date for the fragment (must tt_start_date < x < tt_end_date)
    """
    ical = self.get_icalendar()
    # if we assume that the tt_start_date represents day 1 on the timetable then the difference between it and fragment_start_date in days is the number of 
    start_offset = fragment_start_date - tt_start_date # difference between the term start and the fragment start
    start_offset = start_offset.days + 1 # add one to the offset to account for zero based indexes (i think)
    start_offset = start_offset - ((start_offset / 7) * 2) # divide the whole thing by 7 to get the number of weeks and then subtract the number of weekends
    start_day = start_offset % 10 # modulo 10 should then return the current day number
    # create a list of day numbers to iterate over
    days = range(start_day - 1,10) + range(0,start_day - 1)
    # this is the order of days starting at the start date of the given fragment
    # if the difference between the two fragment dates is less than the length of the days list then truncate the days list
    if (fragment_end_date - fragment_start_date).days > len(days):
      td = (fragment_end_date - fragment_start_date).days
      days = days[0:td-1]
      
    offset = datetime.timedelta(0)
    for day in days:
      r = icalendar.prop.vRecur(FREQ='weekly', INTERVAL=2, UNTIL=fragment_end_date)
      self.create_day(timetable_data[day], day, fragment_start_date + offset, r, ical)
      offset += datetime.timedelta(1)
      while (fragment_start_date + offset).weekday() in [5,6]:
        offset = offset + datetime.timedelta(1)
    return ical
    
    
  def get_icalendar(self):
    """
    Returns an icalendar object that has had several common properties added to it
    """
    ical = icalendar.Calendar()
    ical['uid'] = self.get_uid()
    ical.add('method','publish') # no idea what this is for
    ical.add('version','2.0') # i think this is the version of the ical standard rather than the creating program
    ical.add('calscale','GREGORIAN')
    ical.add('prodid','-//Gower Inc.//TimeChart Conversion Script 0.1//EN')
    itz = icalendar.Timezone()
    itz.add('tzid','Australia/Lindeman')
    itz.add('TZOFFSETFROM',datetime.timedelta(hours=11))
    itz.add('TZOFFSETTO',datetime.timedelta(hours=10))
    ical.add_component(itz)
    return ical
    
  def get_uid(self):
    """
    Generate a uid for the ical uid property
    """
    chars = list(ascii_letters + digits)
    s = ''.join([random.choice(chars) for i in range(16)])
    from icalendar.prop import vText, vDatetime
    return vText('%s-%s@%s' % (vDatetime(datetime.datetime.today()).ical(), s, 'stmonicas.qld.edu.au'))
    

def main():
  # convert dates before passing from command line
  # take a message from the command line to use as the email body
  # take a subject from the command line for the email subject
  # end date needs to be one day more than when you actually want it to end
	i = ICSCreator("./teacher_ttable_export.TXT", datetime.date(2010,4,19), datetime.date(2010,6,26), output_path="/Users/paul/Development/TimeTableExporter/test/")
	i.create_send_all_fragment_timetables(datetime.date(2010,5,10),datetime.date(2010,5,21))


if __name__ == '__main__':
	main()

